package fun.ipconfig;

import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.log4j.Logger;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;

public class MapperRefresh implements Runnable {

    /*初始化配置*/
    private static boolean enabled=true;         // 是否启用Mapper刷新线程功能
    private static int delaySeconds=1;        // 延迟刷新秒数
    private static int sleepSeconds=1;        // 休眠时间
    
    /*==========================================================================*/
    private static Properties prop;
    private static boolean refresh;
    private Set<String> location;    
    private Resource[] mapperLocations;
    private Configuration configuration;   
    private Long beforeTime = 0L;          

    public static Logger log = Logger.getLogger(MapperRefresh.class);
    static {
        prop = new Properties();
    }

    public static boolean isRefresh() {
        return refresh;
    }
    public MapperRefresh(Resource[] mapperLocations, Configuration configuration) {
        this.mapperLocations = mapperLocations;
        this.configuration = configuration;
    }

    @Override
    public void run() {
        beforeTime = System.currentTimeMillis();
        if (enabled) {
            final MapperRefresh runnable = this;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if (location == null){
                        location = new HashSet<>();
                        for (Resource mapperLocation : mapperLocations) {
                            String s = mapperLocation.toString().replaceAll("\\\\", "/");
                            if (!location.contains(s)) {
                                location.add(s.substring(6,s.length()-1));
                                log.info("Location:" + s);
                            }
                        }
                    }
                    try {
                        Thread.sleep(delaySeconds * 1000);
                    } catch (InterruptedException e2) {
                        e2.printStackTrace();
                    }
                    refresh = true;
                    while (true) {
                        try {
                            for (String s : location) {
                                runnable.refresh(s, beforeTime);
                       
                            }
                        } catch (Exception e1) {
                            e1.printStackTrace();
                        }
                        try {
                            Thread.sleep(sleepSeconds * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                }
            }, "MyBatis-Mapper-Refresh").start();
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void refresh(String filePath, Long beforeTime) throws Exception {
        // 本次刷新时间
        Long refrehTime = System.currentTimeMillis();
        File f = new File(filePath);
        boolean isEXistExcep;
        // 通过文件修改时间判断是否修改过
        if (checkFile(new File(filePath), beforeTime)) {
            isEXistExcep=false;//是否有异常的标记
            InputStream inputStream = new FileInputStream(f);
            String resource = f.getAbsolutePath();
            try {
                // 清理原有资源，更新为自己的StrictMap方便，增量重新加载
                String[] mapFieldNames = new String[]{
                        "mappedStatements", "caches",
                        "resultMaps", "parameterMaps",
                        "keyGenerators", "sqlFragments"
                };
                for (String fieldName : mapFieldNames) {
                    Field field = configuration.getClass().getDeclaredField(fieldName);
                    field.setAccessible(true);
                    Map map = ((Map) field.get(configuration));
                    if (!(map instanceof StrictMap)) {
                        Map newMap = new StrictMap(StringUtils.capitalize(fieldName) + "collection");
                        for (Object key : map.keySet()) {
                            try {
                                newMap.put(key, map.get(key));
                            } catch (IllegalArgumentException ex) {
                                newMap.put(key, ex.getMessage());
                            }
                        }
                        field.set(configuration, newMap);
                    }
                }

                // 清理已加载的资源标识，方便让它重新加载。
                Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources");
                loadedResourcesField.setAccessible(true);
                Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
                loadedResourcesSet.remove(resource);

                //重新编译加载资源文件。
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration,
                        resource, configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            } catch (Exception e) {
                isEXistExcep=true;
                throw new NestedIOException("解析mapper.xml资源失败: '" + resource + "'", e);
            } finally {
                ErrorContext.instance().reset();
                if (isEXistExcep){
                     log.info("====================文件"+f.getName()+"格式存在错误，刷新失败，请检查！======================");
                }else{
                    StringBuffer logInfo = new StringBuffer()
                            .append("\n\t===========================================================================================================")
                            .append("\n\t成功刷新Mapper文件: ").append(f.getAbsolutePath())
                            .append("\n\t被刷新的文件名: ").append(f.getName())
                            .append("\n\t===========================================================================================================");
                    log.info(logInfo);
                  
                    
                }
            }
        }
        if (checkFile(f, beforeTime)) {
            this.beforeTime = refrehTime;
        }
        
    }
    
    private boolean checkFile(File file, Long beforeTime) {
        if (file.lastModified() > beforeTime&&file.getName().contains(".xml")) {
            return true;
        }
        return false;
    }
    
    
    public static class StrictMap<V> extends HashMap<String, V> {

        private static final long serialVersionUID = -4950446264854982944L;
        private String name;

        public StrictMap(String name) {
            super();
            this.name = name;
        }
        
        @SuppressWarnings("unchecked")
        @Override
        public V put(String key, V value) {
            // ThinkGem 如果现在状态为刷新，则刷新(先删除后添加)
            if (MapperRefresh.isRefresh()) {
                remove(key);
                MapperRefresh.log.debug("refresh key:" + key.substring(key.lastIndexOf(".") + 1));
            }
            // ThinkGem end
            if (containsKey(key)) {
                throw new IllegalArgumentException(name + " already contains value for " + key);
            }
            if (key.contains(".")) {
                final String shortKey = getShortName(key);
                if (super.get(shortKey) == null) {
                    super.put(shortKey, value);
                } else {
                    super.put(shortKey, (V) new Ambiguity(shortKey));
                }
            }
            return super.put(key, value);
        }
        @Override
        public V get(Object key) {
            V value = super.get(key);
            if (value == null) {
                throw new IllegalArgumentException(name + " does not contain value for " + key);
            }
            if (value instanceof Ambiguity) {
                throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
                        + " (try using the full name including the namespace, or rename one of the entries)");
            }
            return value;
        }

        private String getShortName(String key) {
            final String[] keyparts = key.split("\\.");
            return keyparts[keyparts.length - 1];
        }

        protected static class Ambiguity {
            private String subject;

            public Ambiguity(String subject) {
                this.subject = subject;
            }

            public String getSubject() {
                return subject;
            }
        }
    }
}